/********************************************
 * 功能说明: 
 * 模块名称: 
 * 系统名称: 
 * 软件版权:
 * 系统版本: 1.0.0
 * 开发人员: zhangfb
 * 开发时间: 2018/9/9 16:07
 * 审核人员: 
 * 相关文档: 
 * 修改记录: 修改日期 修改人员 修改说明
 *********************************************/
package com.hyacinth.utils;

import com.hyacinth.structure.ConcurrentReferenceHashMap;
import com.hyacinth.utils.collect.CollectionUtils;
import lombok.extern.slf4j.Slf4j;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 属性加载工具类
 * @author zhangfb
 * @version 1.0.0.1
 * @since JDK 1.8
 */
@Slf4j
public class PropertyUtils {

    /**
     * PropertyDescriptor objects keyed by property name String
     */
    private final Map<String, PropertyDescriptor> propertyDescriptorCache;
    private static final ConcurrentMap<Class<?>, PropertyUtils> strongClassCache = new ConcurrentHashMap<>(64);
    private static final ConcurrentMap<Class<?>, PropertyUtils> softClassCache = new ConcurrentReferenceHashMap<>(64);

    private PropertyUtils(Class<?> beanClass) throws Exception {
        try {
            this.propertyDescriptorCache = new LinkedHashMap<>();
            BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
            // This call is slow so we do it once.
            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor pd : pds) {
                if (Class.class == beanClass && ("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) {
                    // Ignore Class.getClassLoader() and getProtectionDomain() methods - nobody needs to bind to those
                    continue;
                }
                this.propertyDescriptorCache.put(pd.getName(), pd);
            }
            // Explicitly check implemented interfaces for setter/getter methods as well,
            // in particular for Java 8 default methods...
            Class<?> clazz = beanClass;
            while (clazz != null && clazz != Object.class) {
                for (Class<?> ifc : clazz.getInterfaces()) {
                        if (!ClassUtils.isJavaLanguageInterface(ifc)) {
                        for (PropertyDescriptor pd : Introspector.getBeanInfo(ifc).getPropertyDescriptors()) {
                            if (!this.propertyDescriptorCache.containsKey(pd.getName())) {
                                this.propertyDescriptorCache.put(pd.getName(), pd);
                            }
                        }
                    }
                }
                clazz = clazz.getSuperclass();
            }
        } catch (IntrospectionException ex) {
            throw new RuntimeException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
        }
    }

    /**
     * 加载实例对象
     * @param beanClass 对象实例
     */
    public static PropertyUtils forClass(Class<?> beanClass) throws Exception {
        PropertyUtils results = strongClassCache.get(beanClass);
        if (results != null) {
            return results;
        }
        results = softClassCache.get(beanClass);
        if (results != null) {
            return results;
        }
        results = new PropertyUtils(beanClass);
        ConcurrentMap<Class<?>, PropertyUtils> classCacheToUse;
        if (ClassUtils.isCacheSafe(beanClass, PropertyUtils.class.getClassLoader())) {
            classCacheToUse = strongClassCache;
        } else {
            classCacheToUse = softClassCache;
        }
        PropertyUtils existing = classCacheToUse.putIfAbsent(beanClass, results);
        return (existing != null ? existing : results);
    }

    /**
     * 获取所有的属性
     */
    public PropertyDescriptor[] getPropertyDescriptors() {
        PropertyDescriptor[] pds = new PropertyDescriptor[this.propertyDescriptorCache.size()];
        Collection<PropertyDescriptor> values = this.propertyDescriptorCache.values();
        if (!CollectionUtils.isEmpty(values)) {
            pds = values.toArray(pds);
        }
        return pds;
    }

    /**
     * 获取单个属性
     * @param name 属性名称
     */
    public PropertyDescriptor getPropertyDescriptor(String name) {
        PropertyDescriptor pd = this.propertyDescriptorCache.get(name);
        if (pd == null && StringUtils.isNotBlank(name)) {
            // 首字母小写
            pd = this.propertyDescriptorCache.get(StringUtils.uncapitalize(name));
            if (pd == null) {
                // 首字母大写
                pd = this.propertyDescriptorCache.get(StringUtils.capitalize(name));
            }
        }
        return pd;
    }
}
